#-*- coding: utf-8 -*-
__author__ = 'sysprog'
from common import NDW_DEBUG, trace_method, logable


@logable
class ControlFuncInfo(object):
    def __init__(self):
        self.func_call_node_list = []
        self.func_call_map = {}
        self.glob = []
        self.func_var = []
        self.func_func = []

    @trace_method
    def load_from_tu(self, tu):
        import clang.cindex as ci
        from utils.common import walk_ast_tree

        def func_def_visitor(node, parent, **kwargs):
            if node.kind == ci.CursorKind.FUNCTION_DECL:
                global_var_list = []
                func_var = kwargs["func_var"]
                func_func = kwargs["func_func"]
                walk_ast_tree(node, node, data_flow_builder, gv_list=global_var_list, func_var=func_var, func_func=func_func)

                for var in global_var_list:
                    kwargs["gv_link"].append((var, node.spelling))
                return 1

            return 0

        def data_flow_builder(node, parent, **kwargs):
            if node.kind == ci.CursorKind.DECL_REF_EXPR:
                # global variable
                df = node.get_definition()
                if df is None:
                    df = node.referenced
                if df.kind == ci.CursorKind.VAR_DECL:
                    par = df.semantic_parent

                    if par is not None:
                        if par.kind == ci.CursorKind.TRANSLATION_UNIT:
                            kwargs["gv_list"].append(df)
                return 1
            elif node.kind == ci.CursorKind.CALL_EXPR:
                for it in node.get_arguments():
                    is_func_call = False
                    is_var = False

                    if it.kind == ci.CursorKind.CALL_EXPR:
                        func1 = node.displayname
                        func2 = it.displayname
                        kwargs["func_func"].append((func1, func2))

                    elif it.kind == ci.CursorKind.UNEXPOSED_EXPR:
                        var = it.get_definition()
                        func = node.displayname
                        if var is not None:
                            kwargs["func_var"].append((var, func))
            return 0

        walk_ast_tree(tu.cursor, tu.cursor, func_def_visitor, gv_link=self.glob, func_var=self.func_var, func_func=self.func_func)

        self.log(NDW_DEBUG, "global variable usages: %s", self.glob)
        self.log(NDW_DEBUG, "function-var    usages: %s", self.func_var)
        self.log(NDW_DEBUG, "function-function link: %s", self.func_func)


    @trace_method
    def render_image(self, image_file_name):
        import igraph
        g = igraph.Graph(directed=True)

        ########################################################
        #               Add global var usages                  #
        ########################################################
        for var, func in self.glob:
            # add vertex
            try:
                g.vs.find(name=func)
            except Exception:
                self.log(NDW_DEBUG, "add func vertex '%s'", func)
                g.add_vertex(func, label=func, shape="circle", color="gray")
            try:
                g.vs.find(name=var.get_usr())
            except Exception:
                var_name = "%(name)s[%(mod)s]" % {"name": var.displayname, "mod": "GLOBAL"}
                self.log(NDW_DEBUG, "add var vertex '%s' (%s)", var.get_usr(), var_name)
                g.add_vertex(var.get_usr(), label=var_name, shape="rectangle", color="red")

            if not g.are_connected(func, var.get_usr()):
                self.log(NDW_DEBUG, "add link from '%s' to '%s'", func, var.get_usr())
                g.add_edge(func, var.get_usr())

        ########################################################
        #               Add var local var usages               #
        ########################################################
        for var, func in self.func_var:
            try:
                g.vs.find(name=func)
            except Exception:
                self.log(NDW_DEBUG, "add func vertex '%s'", func)
                g.add_vertex(func, label=func, shape="circle", color="gray")

            try:
                g.vs.find(name=var.get_usr())
            except Exception:
                mod = var.semantic_parent.spelling
                label = "%(name)s[%(mod)s]" % {"name": var.displayname, "mod": mod}
                self.log(NDW_DEBUG, "add var vertex '%s' (%s)", var.get_usr(), var_name)
                g.add_vertex(var.get_usr(), label=label, shape="rectangle", color="green")

                ################################################################
                # link between variable and function where this var was defined#
                ################################################################
                self.log(NDW_DEBUG, "link between variable '%s' and function where this var was defined '%s'",
                         var.get_usr(), mod)
                try:
                    g.vs.find(name=mod)
                except Exception:
                    self.log(NDW_DEBUG, "add func vertex '%s'", mod)
                    g.add_vertex(mod, label=mod, shape="circle", color="gray")

                if g.get_eid(mod, var.get_usr(), directed=False, error=False) == -1:
                    self.log(NDW_DEBUG, "add link from '%s' to '%s'", var.get_usr(), mod)
                    g.add_edge(mod, var.get_usr(), color="green")

            if g.get_eid(func, var.get_usr(), directed=False, error=False) == -1:
                self.log(NDW_DEBUG, "add link from '%s' to '%s'", mod, var.displayname)
                g.add_edge(func, var.get_usr())


        ########################################################
        #               Add function <-> function links        #
        ########################################################
        for func1, func2 in self.func_func:
            try:
                g.vs.find(name=func1)
            except Exception:
                self.log(NDW_DEBUG, "add func vertex '%s'", func1)
                g.add_vertex(func1, label=func1, shape="circle", color="gray")
            try:
                g.vs.find(name=func2)
            except Exception:
                self.log(NDW_DEBUG, "add func vertex '%s'", func1)
                g.add_vertex(func2, label=func2, shape="circle", color="gray")

            # add edge
            if not g.are_connected(func1, func2):
                self.log(NDW_DEBUG, "add link from '%s' to '%s'", func2, func1)
                g.add_edge(func1, func2)


        # plot
        layout = g.layout("fr")
        g.vs["size"] = 30
        igraph.plot(g, image_file_name, layout=layout, margin=100, edge_width=2)


